package com.gin.kotlin.no_gay_alliance.fragment.replylist

import android.annotation.SuppressLint
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.view.ViewGroup
import android.webkit.JavascriptInterface
import android.widget.RelativeLayout
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.DrawableRes
import androidx.core.graphics.drawable.toBitmap
import androidx.fragment.app.FragmentActivity
import com.gin.kotlin.no_gay_alliance.R
import com.gin.kotlin.no_gay_alliance.activity.AttachmentActivity
import com.gin.kotlin.no_gay_alliance.activity.ReplyListActivity
import com.gin.kotlin.no_gay_alliance.activity.ReplyListActivity.Companion.KEY_ARGUMENT
import com.gin.kotlin.no_gay_alliance.app.App
import com.gin.kotlin.no_gay_alliance.argument.ReplyListArgument
import com.gin.kotlin.no_gay_alliance.databinding.FragmentReplyPageAbstractBinding
import com.gin.kotlin.no_gay_alliance.enums.NgaTheme
import com.gin.kotlin.no_gay_alliance.listener.ReplyPopupMenuListener
import com.gin.kotlin.no_gay_alliance.model.AttachmentModel
import com.gin.kotlin.no_gay_alliance.model.ReplyModel
import com.gin.kotlin.no_gay_alliance.store.GlobalProperty
import com.gin.kotlin.no_gay_alliance.webview.BbsCodeWebView
import com.gin.kotlin.no_gay_alliance.webview.JsAndroidInterface
import com.gin.kotlin.no_gay_alliance.webview.JsAndroidInterface.Companion.showPopupMenu
import com.gin.kotlin.no_gay_alliance.worker.UserIpLocationWorker
import com.gin.nga.enums.Evaluation
import com.gin.nga.enums.status.AccountStatus
import com.gin.nga.enums.status.ReplyStatus
import com.gin.nga.response.base.NgaResponse
import com.gin.nga.response.reply.ReplyInfo
import com.gin.nga.utils.BbsCodeRenderer
import com.gin.nga.utils.StrUtils
import java.io.ByteArrayOutputStream
import java.util.Base64
import java.util.UUID
import java.util.regex.Pattern


/**
 * 一页回复，单WebView模式
 */
class ReplyPageHtmlFragment : AbstractReplyPageFragment(), JsAndroidInterface, OnTouchListener {

    override val mLauncher: ActivityResultLauncher<Intent>
        get() = launcher
    override val mActivity: FragmentActivity
        get() = this.requireActivity()

    /**
     * 菜单项目的点击响应
     */
    private lateinit var listener: ReplyPopupMenuListener

    /**
     * 当前的响应数据
     */
    private var replies: MutableList<ReplyInfo>? = null

    /**
     * 渲染器
     */
    private var renderer: BbsCodeRenderer? = null
    private lateinit var webView: BbsCodeWebView

    /**
     * 总模板
     */
    private lateinit var template: String

    /**
     * 单个回复模板
     */
    private lateinit var replyTemplate: String

    /**
     * 刷新前保存滚动位置
     */
    private var scrollY: Double? = null

    /**
     * 初始化渲染组件
     */
    @SuppressLint("ClickableViewAccessibility")
    override fun initRenderComponent(b: FragmentReplyPageAbstractBinding) {
        this.webView = (requireActivity() as ReplyListActivity).bbsCodeWebViewPool.obtainWebView()
            .also {
                it.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
                it.addJavascriptInterface(this, "android")
            }
        b.swipeRefreshLayout.addView(this.webView)
        this.template = requireContext().assets.open("template.html").bufferedReader().readText()
        this.replyTemplate = requireContext().assets.open("reply_template.html").bufferedReader().readText()
        this.renderer = BbsCodeRenderer(GlobalProperty.getInstance().domain, App.INSTANCE.emoteGroup)

        webView.setOnTouchListener(this)

        this.listener = ReplyPopupMenuListener(requireContext(), launcher, handler, onlyAuthor = argument.userId, page = argument.page)
    }

    override fun onDestroyView() {
        super.onDestroyView()

        (this.webView.parent as? ViewGroup)?.removeAllViews()
        this.webView.busy = false
        this.webView.loadDataWithBaseURL("")
    }

    /**
     * 刷新
     */
    override fun refresh() {
        // 刷新之前获取元素位置
        webView.evaluateJavascript("window.scrollY") { this.scrollY = it.toDouble() }

        super.refresh()
    }

    /**
     * 请求成功，执行渲染
     * @param replies MutableList<ReplyInfo>?
     */
    override fun onSuccess(replies: MutableList<ReplyInfo>?) {
        webView.visibility = View.VISIBLE

        val globalProperty = GlobalProperty.getInstance()
        // 系统当前是否处于深色模式
        val systemUiModeNight = resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
        // 系统当前主题
        val theme = if (systemUiModeNight && globalProperty.themeFollow) NgaTheme.BLACK else globalProperty.theme

        val rootClass = ""
        this.replies = replies
        val containers = renderReplies(replies, theme)

        val html = template.replace("[根CLASS占位]", rootClass).replace("[回复容器占位]", containers)

        this.webView.loadDataWithBaseURL(html)

        if (scrollY != null) {
            uiDelayed(500) {
                Log.i(TAG, "滚动到位置: $scrollY")
                webView.evaluateJavascript("window.scrollTo(0, ${scrollY})") {}
                scrollY = null
            }
        }

        // 如果指定了回复id 滚动到该回复
        if (argument.page == argument.targetPage) argument.replyId?.takeIf { it > 0 }?.also { scrollToReply(it) }
        if (argument.page == argument.targetPage) argument.targetFloor?.takeIf { it > 0 }?.also { scrollToFloor(it) }
    }

    override fun scrollToFloor(floorNumber: Int) {
        val runnable = { webView.evaluateJavascript("scrollToFloor(${floorNumber})", null) }
        uiDelayed(1000, runnable)
        uiDelayed(2000, runnable)
    }

    override fun scrollToReply(replyId: Long) {
        val runnable = { webView.evaluateJavascript("scrollToReply(${replyId})", null) }
        uiDelayed(1000, runnable)
        uiDelayed(2000, runnable)
    }

    /**
     * 设置指定回复的赞数
     * @param replyId Long
     * @param dc 变动数值
     */
    private fun setAgreeCount(replyId: Long, dc: Int) {
        webView.evaluateJavascript("setAgreeCount(${replyId},${dc})", null)
    }

    /**
     * 渲染一个回复列表
     * @param replies MutableList<ReplyInfo>?
     * @param theme NgaTheme
     * @return String
     */
    private fun renderReplies(replies: List<ReplyInfo>?, theme: NgaTheme): String {
        val banUsers = App.INSTANCE.database.banUserDao().list()
        val banKeywords = App.INSTANCE.database.banKeywordDao().list()
        fun isBlocked(reply: ReplyModel): Boolean {
            // 该楼层回复的用户
            val postByUserId = reply.content?.let { POST_BY_PATTERN.matcher(it) }?.takeIf { it.find() }?.group(1)?.toLongOrNull()

            return reply.block
                    || banUsers?.any { it.userId == reply.userId?.toLongOrNull() } == true
                    || banKeywords?.any { reply.content?.contains(it.value, true) == true } == true
                    || banUsers?.any { it.userId == postByUserId } == true
                    || App.INSTANCE.blockConfig?.users?.any { it.userId == postByUserId } == true
        }


        // 查询用户IP属地
        val ipLocationHistoryDao = App.INSTANCE.database.ipLocationHistoryDao()
        val ipMap = replies?.mapNotNull { it.authorUid?.toLongOrNull() }?.toTypedArray()
            ?.let { ipLocationHistoryDao.queryLastIdByUserId(it) }
            ?.let { ipLocationHistoryDao.queryById(it.toTypedArray()) }
            ?.associate { Pair(it.userId, it.location) } ?: mapOf()

        // 将所有未注销用户添加到更新队列
        replies?.asSequence()?.mapNotNull { it.userInfo }
            ?.filter { it.getAccountStatus() != AccountStatus.DISCARD }
            ?.mapNotNull { it.userId }
            ?.filter { it > 0 }
            ?.distinct()?.toList()?.also { UserIpLocationWorker.start(it) }


        //todo 查询本页回复的历史版本
        val versionMap: Map<Long, String> = mapOf()


        return replies?.joinToString("") { replyInfo ->

            val dataModel = ReplyModel(replyInfo)
            // 背景颜色
            val bgcClass = when (theme) {
                NgaTheme.YELLOW -> if (dataModel.light) "background_color_yellow_light" else "background_color_yellow_dark"
                NgaTheme.GREEN -> if (dataModel.light) "background_color_green_light" else "background_color_green_dark"
                NgaTheme.BLACK -> if (dataModel.light) "background_color_black_light" else "background_color_black_dark"
            }
            val blockRemark = banUsers?.firstOrNull { it.userId == dataModel.userId?.toLongOrNull() }?.remark?.takeIf { it.isNotEmpty() }?.let { ": $it" } ?: ""

            // 容器class，如果是沃顿显示沃顿蓝
            val containerClass = if (theme == NgaTheme.BLACK) "black_text_color" else "default_text_color"
            // 处罚框
            val lessNukeClass = if (!dataModel.punishedInTopic) "" else if (theme == NgaTheme.BLACK) "less_nuke" else "less_nuke_dark"
            // 正文字体颜色
            val contentClass = when (dataModel.isWarden) {
                true -> if (theme == NgaTheme.BLACK) "black_warden_text_color" else "warden_text_color"
                false -> if (theme == NgaTheme.BLACK) "black_text_color" else "default_text_color"
            }
            // 头像
            val avatar =
                if (dataModel.anonymous)
                    obtainImgByDrawableId(R.drawable.ic_person_anonymous_16) ?: ""
                else
                    dataModel.avatarBuff?.url ?: dataModel.avatar ?: obtainImgByDrawableId(R.drawable.ic_person_18) ?: ""

            // 回复状态
            val statusColorRed = "#FF3333"
            val statusColorBlue = if (theme == NgaTheme.BLACK) "#8080F6" else "#4D4DFF"
            val replyStatus = dataModel.replyStatus.joinToString("") {
                val color = if (it.color == R.attr.textColorRed) statusColorRed else statusColorBlue
                "<span style='color:${color}'>[${it.name}]</span>"
            }

            // 楼主标记
            val isHost = !dataModel.userId.isNullOrEmpty() && (dataModel.userId == topic?.authorUid || dataModel.userId == argument.hostId)
            val hostTag = if (!isHost) "" else "<span style='color:${statusColorRed}'>[楼主]</span>"

            //徽章
            val medals = dataModel.medals.takeIf { !it.isNullOrEmpty() }
                ?.joinToString("") { "<img src='$it' style='margin-right:2px' />" }?.let { "<span>徽章</span> $it" } ?: ""
            val gifts = dataModel.gifts.takeIf { !it.isNullOrEmpty() }
                ?.joinToString("") { "<span><img class='gift' src='${it.getUrl()}' style='margin-right:2px' />x${it.count}</span>" } ?: ""

            // 操作记录
            val logs = dataModel.logs?.joinToString("") {
                val color = when (it.evaluation) {
                    Evaluation.POSITIVE -> statusColorBlue
                    Evaluation.NEGATIVE -> statusColorRed
                    Evaluation.NEUTRAL -> ""
                }
                "<div style='color:${color}'>${it.getDescription()}</div>"
            }?.takeIf { it.isNotEmpty() }?.let { "<h3>操作记录</h3><div>$it</div>" } ?: ""

            // 评论贴条
            val comment = renderReplies(dataModel.comment, theme).takeIf { it.isNotEmpty() }
                ?.let { "<h3  class='comment_header'>评论/贴条(${dataModel.comment?.size})</h3><div>$it</div>" } ?: ""
            // 热门回复
            val hotReplies = renderReplies(dataModel.hotReplies, theme).takeIf { it.isNotEmpty() }
                ?.let { "<h3 class='hot_reply_header'>热门回复(${dataModel.hotReplies?.size})</h3><div>$it</div>" } ?: ""

            // 已注销用户点击不进入用户中心
            val centerUserId = if (replyInfo.userInfo?.getAccountStatus() == AccountStatus.DISCARD) -1 else dataModel.userId

            replyTemplate
                .replace("{floor}", dataModel.floor.toString())
                .replace("{replyId}", dataModel.replyId.toString())
                .replace("{topicId}", dataModel.topicId?.toString() ?: UUID.randomUUID().toString())
                .replace("{楼层缺失时隐藏}", if (dataModel.replyId != null) "block" else "none")
                .replace("{标题占位}", StrUtils.escape(dataModel.title) ?: "")
                .replace("{版本class}", if (versionMap.containsKey(dataModel.replyId)) "" else "hidden")
                .replace("{附件class}", if (dataModel.attachments.isNullOrEmpty()) "hidden" else "")
                .replace("{评分占位}", dataModel.score?.let { "评分: $it" } ?: "")
                .replace("{禁言标记}", if (dataModel.muted) "<span style='color:${statusColorRed}'>MUTED</span>" else "")
                .replace("{IP属地}", ipMap[dataModel.userId?.toLongOrNull()]?.let { "<span style='color:${statusColorBlue}'>${it}</span>" } ?: "")
                .replace("{回复状态}", replyStatus)
                .replace("{楼主标记}", hostTag)
                .replace("{屏蔽备注占位}", blockRemark)
                .replace("{forumId}", this.forumId.toString())
                .replace("{userId}", dataModel.userId.toString())
                .replace("{centerUserId}", centerUserId.toString())
                .replace("{操作记录}", logs)
                .replace("{评论贴条}", comment)
                .replace("{热门回复}", hotReplies)
                .replace("{徽章}", medals)
                .replace("{礼物占位}", gifts)
                .replace("{账号状态占位}", dataModel.accountStatus)
                .replace("{正文占位}", renderer?.render(replyInfo.getBbsCodeNode()) ?: "")
                .replace("{背景色CLASS占位}", bgcClass)
                .replace("{lessNukeClass}", lessNukeClass)
                .replace("{容器CLASS占位}", containerClass)
                .replace("{正文CLASS}", contentClass)
                .replace("{用户名占位}", dataModel.username ?: "<span style='font-weight: bold;color:${statusColorRed}'>${getString(R.string.warning_reply_not_found)}</span>")
                .replace("{头像占位}", avatar)
                .replace("{小尾巴占位}", dataModel.avatarBuff?.suffix ?: "")
                .replace("{威望占位}", "${dataModel.prestige} ${dataModel.reputation} ${dataModel.postCount}")
                .replace("{修改时间占位}", dataModel.lastEdit)
                .replace("{发布时间占位}", dataModel.postTime)
                .replace("{警告display}", if (isBlocked(dataModel)) "block" else "none")
                .replace("{楼层display}", if (isBlocked(dataModel)) "none" else "block")
                .replace("{楼层占位}", "${dataModel.client} ${dataModel.floor}楼")
                .replace("{赞数占位}", dataModel.agreeCount)
                .replace("{赞按钮}", obtainImgByDrawableId(R.drawable.ic_agree) ?: "")
                .replace("{版本按钮}", obtainImgByDrawableId(R.drawable.ic_history_24) ?: "")
                .replace("{附件按钮}", obtainImgByDrawableId(R.drawable.ic_attachment_24) ?: "")
                .replace("{查人按钮}", obtainImgByDrawableId(R.drawable.ic_person_18) ?: "")
                .replace("{回复按钮}", obtainImgByDrawableId(R.drawable.ic_reply_24) ?: "")
                .replace("{更多按钮}", obtainImgByDrawableId(R.drawable.ic_more_24) ?: "")
        } ?: ""
    }

    /**
     * 请求错误时的回调，如清空或隐藏渲染组件
     */
    override fun onError() {

        Log.w(TAG, "onError: 请求错误时的回调，如清空或隐藏渲染组件")
        webView.visibility = View.GONE
    }

    override fun agreeCallback(replyId: Long, body: NgaResponse<List<String>>?) {
        // 失败
        body?.error?.get(0)?.apply { toast(this) }
        // 成功
        body?.data?.apply {
            toast(this[0])
            setAgreeCount(replyId, this[1].toInt())
        }


    }

    /**
     * 打开查询用户菜单：只看TA，用户主题，本版主题，本版精华，用户回复，本版回复
     */
    @JavascriptInterface
    override fun showQueryUserMenu(replyId: Long) {
        findReplyInfo(replyId)?.also {
            showPopupMenu(
                requireContext(), b!!.popupMenuAnchor, R.menu.query_user, listener.copy(
                    forumId = forumId,
                    forumName = forumName,
                    replyInfo = it,
                )
            )
        }
    }


    /**
     * 打开更多菜单
     * @param replyId Long
     */
    @JavascriptInterface
    override fun showMoreMenu(replyId: Long) {
        findReplyInfo(replyId)?.also {
            ui {
                showPopupMenu(requireContext(), b!!.popupMenuAnchor, R.menu.more, listener.copy(replyInfo = it, topic = topic)).also { menu ->
                    // 根据提示状态隐藏一个菜单项
                    if (it.getStatus()?.contains(ReplyStatus.NO_HINT) == true) {
                        menu.menu.findItem(R.id.mi_notice_off)
                    } else {
                        menu.menu.findItem(R.id.mi_notice_on)
                    }.isVisible = false
                }
            }
        }
    }

    @JavascriptInterface
    override fun showAttachments(replyId: Long) {
        findReplyInfo(replyId)?.also { r -> AttachmentActivity.startActivity(requireContext(), r.attachments?.map { AttachmentModel(it) } ?: return) }
    }

    /**
     * 根据回复id返回回复信息
     * @param replyId Long
     * @return ReplyInfo?
     */
    private fun findReplyInfo(replyId: Long) = mutableListOf<ReplyInfo>().also { list ->
        replies?.also { list.addAll(it) }
        replies?.flatMap { it.hotReplies ?: listOf() }?.also { list.addAll(it) }
        replies?.flatMap { it.comment ?: listOf() }?.also { list.addAll(it) }
    }.firstOrNull { it.replyId == replyId }

    /**
     * 将drawable转换为base64
     * @param drawableId Int
     * @return String?
     */
    private fun obtainImgByDrawableId(@DrawableRes drawableId: Int) = getDrawable(drawableId)?.toBitmap()?.let { "data:image/png;base64,${bitmapToBase64(it)}" }

    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        //设置一个TextView，跟随点击位置移动，提供给PopupMenu定位
        if (event?.action == MotionEvent.ACTION_DOWN) {
            val x = event.x
            val y = event.y

//            Log.i(TAG, "onTouch: ($x,$y)")
            b?.popupMenuAnchor?.apply {
                val layoutParams = this.layoutParams as RelativeLayout.LayoutParams
                layoutParams.leftMargin = x.toInt()
                layoutParams.topMargin = y.toInt()
                this.layoutParams = layoutParams
                this.requestLayout()
            }
        }
        v?.performClick()
        return false
    }

    companion object {
        private val POST_BY_PATTERN = Pattern.compile("Post by \\[uid=(\\d+)]")

        fun newInstance(argument: ReplyListArgument) = ReplyPageHtmlFragment().also { it.arguments = Bundle().also { b -> b.putParcelable(KEY_ARGUMENT, argument) } }

        fun bitmapToBase64(bitmap: Bitmap): String {
            val byteArrayOutputStream = ByteArrayOutputStream()
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
            val byteArray = byteArrayOutputStream.toByteArray()
            return Base64.getEncoder().encodeToString(byteArray)
        }
    }

}